﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using RoketPack.Management;
using System.Net;
using RoketPack.Collections;
using System.IO;
using System.Xml;
using System.Threading;

namespace RoketPack
{
    public partial class RepositoryManager : UserControl
    {
        private List<Repository> p_Repositories = new List<Repository>();
        private int p_SelectedIndex = -1;
        private Repository p_SelectedItem = null;
        private bool m_MustRealignRemoveButtonOnNextPaint = false;
        private bool p_AllowModifications = true;

        private static Color m_MenuHighlight = Color.FromArgb(255, 51, 51, 51);
        private static Color m_Window = Color.FromArgb(255, 255, 255, 255);
        private static Color m_ControlText = Color.FromArgb(255, 0, 0, 0);
        private static Color m_ControlLight = Color.FromArgb(255, 212, 208, 200);
        private static Color m_ControlDark = Color.FromArgb(255, 128, 128, 128);

        public RepositoryManager()
        {
            InitializeComponent();
            this.c_AddButton.Location = new Point(this.Width - 7 - this.c_AddButton.Width, 8);
            this.c_AddUrlTextBox.Location = new Point(7, 8);
            this.c_AddUrlTextBox.Size = new Size(this.Width - 7 - this.c_AddButton.Width - 14, this.c_AddButton.Height);
            this.c_AddUrlTextBox.Select(this.c_AddUrlTextBox.Text.Length, 0);
        }

        /// <summary>
        /// Called when the repository manager needs to be redrawn.
        /// </summary>
        private void RepositoryManager_Paint(object sender, PaintEventArgs e)
        {
            int h = 1;

            e.Graphics.FillRectangle(
                new SolidBrush(Color.FromArgb(255, 240, 240, 240)),
                new Rectangle(0, 0, this.Width, this.Height)
                );

            Font bold = new Font(this.Font, FontStyle.Bold);
            foreach (Repository r in this.p_Repositories)
            {
                Color back = Color.FromArgb(255, 240, 240, 240);
                Color fore = RepositoryManager.m_ControlText;
                int height = 26;
                if (r == this.p_SelectedItem)
                {
                    back = RepositoryManager.m_MenuHighlight;
                    fore = RepositoryManager.m_Window;
                    height = 68;
                }

                int installed = 0;
                int total = 0;
                if (r.Applications != null)
                {
                    foreach (RoketPack.Management.Application a in r.Applications.Values)
                    {
                        total += 1;
                        if (a.IsInstalled())
                            installed += 1;
                    }
                }

                e.Graphics.FillRectangle(
                    new SolidBrush(back),
                    new Rectangle(0, h, this.Width, height - 1)
                    );

                TextRenderer.DrawText(e.Graphics, r.Name, bold, new Point(4, h + 6), fore);
                TextRenderer.DrawText(
                    e.Graphics,
                    installed + " / " + total + " applications installed",
                    this.Font,
                    new Point(
                        this.Width - 4 - TextRenderer.MeasureText(
                            e.Graphics,
                            installed + " / " + total + " applications installed", 
                            this.Font).Width,
                        h + 6),
                    fore
                    );

                Color secolor = Color.OrangeRed;
                string setext = "Insecure";
                if (new Uri(r.Url).Scheme == "https")
                {
                    if (r == this.p_SelectedItem)
                    {
                        secolor = Color.ForestGreen;
                        secolor = Color.FromArgb(
                            secolor.A,
                            secolor.R + 60,
                            secolor.G + 60,
                            secolor.B + 60
                            );
                    }
                    else
                        secolor = Color.ForestGreen;
                    setext = "Secure";
                }
                TextRenderer.DrawText(
                    e.Graphics,
                    setext,
                    bold,
                    new Point(
                        4 + TextRenderer.MeasureText(
                            e.Graphics,
                            r.Name,
                            bold).Width,
                        h + 6),
                    secolor
                    );

                Pen p = new Pen(RepositoryManager.m_ControlLight, 1.0f);
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                e.Graphics.DrawLine(p, 0, h + height - 1, this.Width, h + height - 1);

                // Draw more information for the selected item.
                if (r == this.p_SelectedItem)
                {
                    TextRenderer.DrawText(e.Graphics, r.Url, this.Font, new Point(4, h + 6 + 20), RepositoryManager.m_ControlDark);
                    TextRenderer.DrawText(e.Graphics, r.Description, this.Font, new Point(4, h + 6 + 40), fore);

                    if (this.m_MustRealignRemoveButtonOnNextPaint)
                    {
                        if (this.p_AllowModifications)
                            this.c_RemoveButton.Visible = true;
                        this.c_RemoveButton.Enabled = (r.Url != Manager.OfficialRepository);
                        this.c_RemoveButton.Location = new Point(this.Width - 7 - this.c_RemoveButton.Width, h + 26);
                        this.m_MustRealignRemoveButtonOnNextPaint = false;
                    }
                }
                h += height;
            }

            Point b = new Point(this.Width - 7 - this.c_AddButton.Width, h + 7);
            if (this.c_AddButton.Location != b)
            {
                this.c_AddButton.Location = b;
                this.c_AddUrlTextBox.Location = new Point(7, h + 7);
                this.c_AddUrlTextBox.Size = new Size(this.Width - 7 - this.c_AddButton.Width - 14, this.c_AddButton.Height);
            }

            Pen o = new Pen(RepositoryManager.m_ControlDark, 1.0f);
            e.Graphics.DrawRectangle(o,
                new Rectangle(0, 0, this.Width - 1, this.Height - 1)
                );
        }

        /// <summary>
        /// A list of repositories that this RepositoryManager shows and
        /// allows the user to modify.
        /// </summary>
        internal List<Repository> Repositories
        {
            get
            {
                return this.p_Repositories;
            }
            set
            {
                this.p_Repositories = value;
            }
        }

        /// <summary>
        /// Whether or not the Repository Manager should allow modifications to be made to the
        /// repository list (such as Add / Remove).
        /// </summary>
        [DefaultValue(true)]
        public bool AllowModifications
        {
            get
            {
                return this.p_AllowModifications;
            }
            set
            {
                this.p_AllowModifications = value;
                if (this.p_AllowModifications)
                {
                    this.c_AddButton.Visible = true;
                    this.c_AddUrlTextBox.Visible = true;
                    if (this.p_SelectedItem != null)
                        this.c_RemoveButton.Visible = true;
                }
                else
                {
                    this.c_AddButton.Visible = false;
                    this.c_AddUrlTextBox.Visible = false;
                    this.c_RemoveButton.Visible = false;
                }
            }
        }

        /// <summary>
        /// The current index of the selected item.
        /// </summary>
        public int SelectedIndex
        {
            get
            {
                return this.p_SelectedIndex;
            }
            set
            {
                if (value != -1)
                    this.p_SelectedItem = this.p_Repositories[value];
                this.p_SelectedIndex = value;
            }
        }

        /// <summary>
        /// Called when the user clicks the mouse on the control.  Used to change the
        /// currently selected repository.
        /// </summary>
        private void RepositoryManager_MouseClick(object sender, MouseEventArgs e)
        {
            this.p_SelectedItem = null;
            this.p_SelectedIndex = -1;
            this.c_RemoveButton.Visible = false;

            int h = 0;
            int c = 0;

            if (this.c_AddUrlTextBox.Focused)
                this.c_AddButton.Focus();

            foreach (Repository r in this.p_Repositories)
            {
                int height = 26;
                if (r == this.p_SelectedItem)
                    height = 64;

                if (e.Y > h && e.Y < h + height)
                {
                    this.p_SelectedItem = r;
                    this.p_SelectedIndex = this.p_Repositories.IndexOf(r);
                    this.Refresh();

                    // Move the Remove button to this location.
                    if (this.p_AllowModifications)
                        this.c_RemoveButton.Visible = true;
                    this.c_RemoveButton.Enabled = (r.Url != Manager.OfficialRepository);
                    this.c_RemoveButton.Location = new Point(this.Width - 7 - this.c_RemoveButton.Width, c + 26);

                    break;
                }

                h += height;
                c += 26;
            }

            if (this.p_SelectedItem == null)
                this.Refresh();
        }

        /// <summary>
        /// Called when the RepositoryManager is resized and we need to redraw and
        /// rearrange subcontrols.
        /// </summary>
        private void RepositoryManager_Resize(object sender, EventArgs e)
        {
            this.m_MustRealignRemoveButtonOnNextPaint = true;
            this.Refresh();
        }

        /// <summary>
        /// Called when the Remove button is pressed.  Handles removing a
        /// repository from our list.
        /// </summary>
        private void c_RemoveButton_Click(object sender, EventArgs e)
        {
            if (!this.p_AllowModifications)
                return;

            if (MessageBox.Show(
                "Are you sure you want to delete this repository?  Applications " +
                "that are installed by this repository will not be removed.  You will " +
                "need to remove them manually via Control Panel if there are any " +
                "installed.",
                "Delete Repository?",
                MessageBoxButtons.YesNo,
                MessageBoxIcon.Warning,
                MessageBoxDefaultButton.Button2) == DialogResult.Yes)
            {
                this.p_Repositories.Remove(this.p_SelectedItem);
                if (this.p_SelectedIndex >= this.p_Repositories.Count)
                    this.p_SelectedIndex = this.p_Repositories.Count - 1;
                if (this.p_Repositories.Count == 0)
                {
                    this.p_SelectedItem = null;
                    this.p_SelectedIndex = -1;
                }
                else
                    this.p_SelectedItem = this.p_Repositories[this.p_SelectedIndex];

                this.m_MustRealignRemoveButtonOnNextPaint = true;
                this.Refresh();
            }
        }

        /// <summary>
        /// Called when the Add button is clicked.  Handles adding a repository to
        /// our list of repositories.
        /// </summary>
        private void c_AddButton_Click(object sender, EventArgs e)
        {
            if (!this.p_AllowModifications)
                return;

            try
            {
                Uri uo = new Uri(this.c_AddUrlTextBox.Text.TrimEnd('/'));
                Uri u = new Uri(this.c_AddUrlTextBox.Text.TrimEnd('/') + "/repository.xml");

                if (u.Scheme != "http" && u.Scheme != "https")
                    throw new UriFormatException("Must use HTTP or HTTPS protocol.");

                foreach (Repository r in this.p_Repositories)
                    if (new Uri(r.Url) == uo)
                    {
                        throw new DuplicateNameException();
                    }

                this.c_AddButton.Enabled = false;
                this.c_RemoveButton.Enabled = false;
                this.c_AddUrlTextBox.Enabled = false;

                ThreadStart lambda = () =>
                {
                    try
                    {
                        ConfigurationReader c = new ConfigurationReader();
                        Repository re = c.GetRepository(uo);

                        // Everything looks good if we get to here.
                        re.Initalize();
                        Manager.VoidLambda reenable = () =>
                        {
                            this.p_Repositories.Add(re);
                            this.c_AddUrlTextBox.Text = "https://";

                            this.Refresh();
                        };
                        this.Invoke(reenable);
                    }
                    catch (XmlException)
                    {
                        // TODO: We really need to show more information about the XML error here (and correctly
                        // detect situations with 404 errors).  We'll leave it until the next version.
                        MessageBox.Show("One or more XML documents in the repository are malformed.", "Malformed Repository",
                            MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                    }
                    catch (NullReferenceException)
                    {
                        MessageBox.Show("One or more XML documents in the repository are malformed.", "Malformed Repository",
                            MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                    }
                    catch (WebException ex)
                    {
                        if (ex.Status == WebExceptionStatus.SecureChannelFailure || ex.Status == WebExceptionStatus.TrustFailure)
                        {
                            MessageBox.Show("This repository does not support the HTTPS (secure) protocol.  It is \r\n" +
                                            "possible that the repository's SSL certificate has expired or is invalid.", "SSL Error",
                                MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                        }
                        else if (ex.Status == WebExceptionStatus.ProtocolError)
                        {
                            MessageBox.Show(@"Unable to find the document:
" + ex.Response.ResponseUri.ToString() + @"
on the remote server.  Ensure all document references are valid.", "Repository Not Responding",
                                MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                        }
                        else
                        {
                            MessageBox.Show(@"A WebException occurred while accessing the repository.
Exception Status: " + ex.Status.ToString() + @"
Ensure all document references are valid.", "Repository Not Responding",
                                MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                        }
                    }
                    catch (RoketPack.Management.Objects.RecursiveVariableDependencyFoundException)
                    {
                        MessageBox.Show(@"One or more of the applications in the repository has a
recursive variable dependency situation.  Inform the repository owner.", "Malformed Repository",
                            MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
                    }
                    catch (Exception ex)
                    {
                        Program.HandleUnhandledException(ex);
                        System.Windows.Forms.Application.Exit();
                    }
                    finally
                    {
                        Manager.VoidLambda reenable = () =>
                        {
                            this.c_AddButton.Enabled = true;
                            this.c_RemoveButton.Enabled = true;
                            this.c_AddUrlTextBox.Enabled = true;

                            this.Refresh();
                        };
                        this.Invoke(reenable);
                    }
                };

                Thread thrd = new Thread(lambda);
                thrd.Name = "Repository Installation Thread";
                thrd.IsBackground = true;
                thrd.Start();
            }
            catch (UriFormatException)
            {
                MessageBox.Show("You must enter a valid URL that uses the HTTP or HTTPS protocol.", "Invalid URI",
                    MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
            }
            catch (DuplicateNameException)
            {
                MessageBox.Show("This repository is already installed.", "Already Installed",
                    MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1);
            }
        }
    }
}
